/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "xpm_normal_hap_test.h"

#include <cerrno>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "file_ex.h"
#include "xpm_common.h"

using namespace std;
using namespace std::chrono;
using namespace testing;
using namespace testing::ext;

namespace OHOS {
namespace Security {
namespace XpmUnitTest {
static int g_fd;
static struct XpmRegionArea g_area;
static unsigned long g_dataOffset, g_dataSize;
static unsigned long g_codeOffset, g_codeSize;

void XpmNormalHapTest::SetUpTestCase()
{
    SaveStringToFile(SELINUX_MODE_PATH, PERMISSIVE_MODE);
    g_fd = open(XPM_LIB_PATH.c_str(), O_RDWR);

    int ret = GetDataSegment(XPM_LIB_PATH, g_dataOffset, g_dataSize);
    ASSERT_EQ(0, ret) << "GetDataSegment failed: " << ret;

    ret = GetCodeSegment(XPM_LIB_PATH, g_codeOffset, g_codeSize);
    ASSERT_EQ(0, ret) << "GetCodeSegment failed: " << ret;

    ret = GetXpmRegion(g_area);
    ASSERT_EQ(0, ret) << "GetXpmRegion failed: " << ret;

    SaveStringToFile(XPM_DEBUG_FS_MODE_PATH, ENFORCE_MODE);
}

void XpmNormalHapTest::TearDownTestCase()
{
    close(g_fd);
    SaveStringToFile(XPM_DEBUG_FS_MODE_PATH, PERMISSIVE_MODE);
    SaveStringToFile(SELINUX_MODE_PATH, ENFORCE_MODE);
}

void XpmNormalHapTest::SetUp()
{
}

void XpmNormalHapTest::TearDown()
{
}

/**
 * @tc.name: XpmRegionAllocTest
 * @tc.desc: 安全内存分配测试
 * tc.result: 分配成功，且进程对应/proc/pid/xpm_region范围有值
 */
HWTEST_F(XpmNormalHapTest, XpmRegionAllocTest, TestSize.Level0)
{
    struct XpmRegionArea area = {0};

    // 1. 初始化安全内存
    int ret = GetXpmRegion(area);
    EXPECT_EQ(0, ret);

    EXPECT_NE(0UL, area.start);
    EXPECT_NE(0UL, area.end);
}

/**
 * @tc.name: XpmRegionAllocMultiTest
 * @tc.desc: 安全内存多次分配测试
 * tc.result: 调用成功，但是安全内存并未重新分配
 */
HWTEST_F(XpmNormalHapTest, XpmRegionAllocMultiTest, TestSize.Level0)
{
    struct XpmRegionArea area1 = {0};
    struct XpmRegionArea area2 = {0};

    // 1. 第一次初始化安全内存
    int ret = GetXpmRegion(area1);
    EXPECT_EQ(0, ret);

    // 1. 第二次初始化安全内存
    ret = GetXpmRegion(area2);
    EXPECT_EQ(0, ret);

    EXPECT_EQ(area1.start, area2.start);
    EXPECT_EQ(area2.end, area2.end);
}

/**
 * @tc.number: 30
 * @tc.name: FileMmapXpmFlagTest
 * @tc.desc: ABC代码指定MAP_XPM映射到安全内存
 * @tc.result: ABC代码映射成功，映射地址在安全内存范围内
 */
HWTEST_F(XpmNormalHapTest, FileMmapXpmFlagTest, TestSize.Level0)
{
    // 1. 映射只读权限到安全内存
    void *addr = mmap(NULL, g_dataSize, PROT_READ, MAP_PRIVATE | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr);

    // 2. 映射数据段地址在安全内存中
    bool ret = IsXpmRegionInner(reinterpret_cast<unsigned long>(addr),
        reinterpret_cast<unsigned long>(addr) + g_dataSize);
    EXPECT_TRUE(ret);

    munmap(addr, g_dataSize);
}

/**
 * @tc.number: 31
 * @tc.name: FileMmapXpmFlagLimitTest
 * @tc.desc: ABC代码指定MAP_XPM映射超过安全内存大小的size到安全内存
 * @tc.result: ABC代码映射失败
 */
HWTEST_F(XpmNormalHapTest, FileMmapXpmFlagLimitTest, TestSize.Level0)
{
    // 1. 映射超过安全内存大小的范围到安全内存
    void *addr = mmap(NULL, XPM_REGION_LEN + PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_EQ(MAP_FAILED, addr);
}

/**
 * @tc.number: 32
 * @tc.name: FileMmapXpmAddrTest
 * @tc.desc: ABC代码通过指定xpm_region地址映射到安全内存
 * @tc.result: ABC代码映射成功，映射地址在安全内存范围外
 */
HWTEST_F(XpmNormalHapTest, FileMmapXpmAddrTest, TestSize.Level0)
{
    // 1. 通过指定安全内存的地址映射到安全内存中
    void *addr = mmap((void *)g_area.start, g_dataSize, PROT_READ, MAP_PRIVATE, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr);

    // 2. 映射范围不在安全内存中
    bool ret = IsXpmRegionOuter((unsigned long)addr, (unsigned long)addr + g_dataSize);
    EXPECT_TRUE(ret);

    munmap(addr, g_dataSize);
}

/**
 * @tc.number: 32
 * @tc.name: FileMmapXpmAddrTest
 * @tc.desc: ABC代码指定xpm region地址和MAP_FIXED映射到安全内存
 * @tc.result: ABC代码映射失败，安全内存地址不允许MAP_FIXED强制指定
 */
HWTEST_F(XpmNormalHapTest, FileMmapXpmAddrAndFixedFlagTest, TestSize.Level0)
{
    // 1. 强制指定安全内存的地址和MAP_FIXED映射数据段到安全内存中
    void *addr = mmap((void *)g_area.start, g_dataSize, PROT_READ, MAP_PRIVATE | MAP_FIXED, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_EQ(MAP_FAILED, addr);

    // 2. 左边界行为
    addr = mmap((void *)(g_area.start - PAGE_SIZE), 2 * PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_FIXED, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_EQ(MAP_FAILED, addr);

    // 3. 右边界行为
    addr = mmap((void *)(g_area.end - PAGE_SIZE), 2 * PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_FIXED, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_EQ(MAP_FAILED, addr);
}

/**
 * @tc.number: 33
 * @tc.name: FileMmapAddrAndXpmFlagTest
 * @tc.desc: ABC代码指定地址&MAP_XPM标识文件映射到安全内存
 * @tc.result: ABC代码映射失败，安全内存映射不允许指定地址
 */
HWTEST_F(XpmNormalHapTest, FileMmapAddrAndXpmFlagTest, TestSize.Level0)
{
    // 1. 通过执行地址和MAP_XPM映射到安全内存
    void *addr = mmap((void *)g_area.start, g_dataSize, PROT_READ, MAP_PRIVATE | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_EQ(MAP_FAILED, addr);
}

/**
 * @tc.number: 34
 * @tc.name: FileMmapRoTest
 * @tc.desc: ABC代码映射只读权限到安全内存
 * @tc.result: ABC代码映射成功，安全内存允许映射读权限
 */
HWTEST_F(XpmNormalHapTest, FileMmapRoTest, TestSize.Level0)
{
    // 1、 通过指定MAP_XPM映射只读权限到安全内存
    void *addr = mmap(NULL, g_dataSize, PROT_READ, MAP_PRIVATE | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr);

    // 2. 映射地址在安全内存范围内
    bool ret = IsXpmRegionInner((unsigned long)addr, (unsigned long)addr + g_dataSize);
    EXPECT_TRUE(ret);

    munmap(addr, g_dataSize);
}

/**
 * @tc.number: 35
 * @tc.name: FileMmapRwxTest
 * @tc.desc: ABC代码映射读&写&执行权限到安全内存
 * @tc.result: ABC代码映射失败，安全内存不允许映射写和执行权限
 */
HWTEST_F(XpmNormalHapTest, FileMmapRwxTest, TestSize.Level0)
{
    // 1. 通过指定MAP_XPM私有映射写权限到安全内存中
    void *addr = mmap(NULL, g_dataSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    ASSERT_NE(MAP_FAILED, addr);

    // 2. 通过指定MAP_XPM共享映射写权限到安全内存中
    addr = mmap(NULL, g_dataSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    ASSERT_NE(MAP_FAILED, addr);

    // 3. 通过指定MAP_XPM私有映射执行权限到安全内存中
    addr = mmap(NULL, g_dataSize, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    ASSERT_EQ(MAP_FAILED, addr);

    // 4. 通过指定MAP_XPM共享映射执行权限到安全内存中
    addr = mmap(NULL, g_dataSize, PROT_READ | PROT_EXEC, MAP_SHARED | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    ASSERT_EQ(MAP_FAILED, addr);

    // 5. 通过指定MAP_XPM私有映射执行&写权限到安全内存中
    addr = mmap(NULL, g_dataSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    ASSERT_EQ(MAP_FAILED, addr);

    // 6. 通过指定MAP_XPM共享映射执行&写权限到安全内存中
    addr = mmap(NULL, g_dataSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    ASSERT_EQ(MAP_FAILED, addr);
}

/**
 * @tc.number 36
 * @tc.name: FileRemapXpmRegionLimitTest
 * @tc.desc: ABC代码重映射超过xpm_region范围的大小到xpm region内
 * @tc.result: ABC代码重映射失败
 */
HWTEST_F(XpmNormalHapTest, FileRemapXpmRegionLimitTest, TestSize.Level0)
{
    // 1. 映射普通只读内存
    void *addr1 = mmap(NULL, g_dataSize, PROT_READ, MAP_PRIVATE, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr1);

    // 2. 强制重映射只读内存到xpm region范围内
    void *addr2 = mremap(addr1, g_dataSize, XPM_REGION_LEN + PAGE_SIZE, MREMAP_MAYMOVE | MREMAP_FIXED, g_area.start);
    EXPECT_EQ(MAP_FAILED, addr2);
    EXPECT_EQ(EFAULT, errno);

    // 3. 左边界
    addr2 = mremap(addr1, g_dataSize, XPM_REGION_LEN, MREMAP_MAYMOVE | MREMAP_FIXED, g_area.start - PAGE_SIZE);
    EXPECT_EQ(MAP_FAILED, addr2);
    EXPECT_EQ(EFAULT, errno);

    // 3. 右边界
    addr2 = mremap(addr1, g_dataSize, XPM_REGION_LEN, MREMAP_MAYMOVE | MREMAP_FIXED, g_area.start + PAGE_SIZE);
    EXPECT_EQ(MAP_FAILED, addr2);
    EXPECT_EQ(EFAULT, errno);

    int ret = AccessData(addr1, OP_READ);
    EXPECT_EQ(0, ret);

    // release resource
    munmap(addr1, g_dataSize);
}

/**
 * @tc.number 39
 * @tc.name: FileRemapDataXpmRegionToOutRxTest
 * @tc.desc: ABC数据段重映射到xpm region外，并添加执行权限
 * @tc.result: ABC数据段映射失败，数据段无法添加执行权限
 */
HWTEST_F(XpmNormalHapTest, FileRemapDataXpmRegionToOutRxTest, TestSize.Level0)
{
    // 1. 映射r--权限到安全内存
    void *addr1 = mmap(NULL, g_dataSize, PROT_READ, MAP_SHARED | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr1);

    // 2. 映射r-x权限到安全内存
    void *addr2 = mmap(NULL, g_dataSize, PROT_READ | PROT_EXEC, MAP_SHARED, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_EQ(MAP_FAILED, addr2);

    munmap(addr1, g_dataSize);
}

/**
 * @tc.number 40
 * @tc.name: FileRemapCodeXpmRegionToOutRxTest
 * @tc.desc: ABC数据段重映射到xpm region外，并添加执行权限
 * @tc.result: ABC数据段映射失败，数据段无法添加执行权限
 */
HWTEST_F(XpmNormalHapTest, FileRemapCodeXpmRegionToOutRxTest, TestSize.Level0)
{
    // 1. 映射r--权限到安全内存
    void *addr1 = mmap(NULL, g_codeSize, PROT_READ, MAP_SHARED | MAP_XPM, g_fd, g_codeOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr1);

    // 2. 映射r-x权限到非安全内存
    void *addr2 = mmap(NULL, g_codeSize, PROT_READ | PROT_EXEC, MAP_SHARED, g_fd, g_codeOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr2);

    int ret = ExecSumFun(addr2);
    EXPECT_EQ(0, ret);

    munmap(addr1, g_codeSize);
    munmap(addr2, g_codeSize);
}

/**
 * @tc.number 41
 * @tc.name: FileMprotectRwxTest
 * @tc.desc: ABC代码通过mprotect添加写或执行权限
 * @tc.result: ABC代码在添加写或执行权限时失败，安全内存无法添加写&执行权限
 */
HWTEST_F(XpmNormalHapTest, FileMprotectRwxTest, TestSize.Level0)
{
    // 1. 映射只读数据到安全内存
    void *addr = mmap(NULL, g_dataSize, PROT_READ, MAP_PRIVATE | MAP_XPM, g_fd, g_dataOffset & PAGE_MASK);
    EXPECT_NE(MAP_FAILED, addr);

    // 2. 增加写权限
    int ret = mprotect(addr, g_dataSize, PROT_READ | PROT_WRITE);
    EXPECT_EQ(0, ret);

    // 3. 增加执行权限
    ret = mprotect(addr, g_dataSize, PROT_READ | PROT_EXEC);
    EXPECT_EQ(-1, ret);

    // 4. 增加写&执行权限
    ret = mprotect(addr, g_dataSize, PROT_READ | PROT_WRITE | PROT_EXEC);
    EXPECT_EQ(-1, ret);

    munmap(addr, g_dataSize);
}

/**
 * @tc.number: 42
 * @tc.name: AnonMmapXpmFlagTest
 * @tc.desc: ABC代码使用MAP_XPM匿名映射到安全内存（私有映射&共享映射）
 * @tc.result: ABC代码映射失败，安全内存不允许匿名映射
 */
HWTEST_F(XpmNormalHapTest, AnonMmapXpmFlagTest, TestSize.Level0)
{
    // 1. 匿名映射只读权限
    void *addr = mmap(NULL, ANON_SIZE, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_XPM, -1, 0);
    ASSERT_EQ(MAP_FAILED, addr);

    // 2. 匿名映射只读权限到安全内存
    addr = mmap(NULL, ANON_SIZE, PROT_READ, MAP_SHARED | MAP_ANONYMOUS | MAP_XPM, -1, 0);
    ASSERT_EQ(MAP_FAILED, addr);

    // 3. 匿名映射读&写权限到安全内存
    addr = mmap(NULL, ANON_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS | MAP_XPM, -1, 0);
    ASSERT_EQ(MAP_FAILED, addr);
}

/**
 * @tc.number: 43
 * @tc.name: AnonMmapXpmAddrTest
 * @tc.desc: ABC代码指定安全内存地址映射内存
 * @tc.result: ABC代码映射成功，映射区域不在安全内存中
 */
HWTEST_F(XpmNormalHapTest, AnonMmapXpmAddrTest, TestSize.Level0)
{
    // 1. 匿名映射到安全内存地址范围
    void *addr = mmap((void *)g_area.start, ANON_SIZE, PROT_READ, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    ASSERT_NE(MAP_FAILED, addr);

    // 2. 匿名映射地址不在安全内存范围内
    bool ret = IsXpmRegionOuter((unsigned long)addr, (unsigned long)addr + PAGE_SIZE);
    EXPECT_TRUE(ret);
    munmap(addr, PAGE_SIZE);
}

} // namespace XpmUnitTest
} // namespace Security
} // namespace OHOS